import time
from clogger import logger
from typing import Optional
from schedule import Scheduler
from apps.task.models import JobModel
from django.conf import settings
from cec_base.event import Event
from cec_base.consumer import Consumer
from cec_base.cec_client import StoppableThread
from sysom_utils import AsyncEventExecutor, CecAsyncConsumeTask, ConfigParser
from asgiref.sync import sync_to_async
from datetime import datetime, timedelta
from django.db.models import Q
from .helper import DiagnosisHelper, DiagnosisTaskResult, DiagnosisJobResult


class DiagnosisTaskExecutor(AsyncEventExecutor):
    def __init__(self, config: ConfigParser):
        super().__init__(settings.SYSOM_CEC_PRODUCER_URL, callback=self.process_event)
        self._check_task_schedule: Scheduler = Scheduler()
        self._check_task_process_thread: Optional[StoppableThread] = None
        self._check_interval: int = settings.CHECK_INTERVAL
        self._task_execute_timeout: int = settings.TASK_EXECUTE_TIMEOUT
        self.append_group_consume_task(
            settings.SYSOM_CEC_DIAGNOSIS_TASK_DISPATCH_TOPIC,
            settings.SYSOM_CEC_DIAGNOSIS_CONSUMER_GROUP,
            Consumer.generate_consumer_id(),
            ensure_topic_exist=True,
        )

    async def process_event(self, event: Event, task: CecAsyncConsumeTask):
        try:
            if task.topic_name == settings.SYSOM_CEC_DIAGNOSIS_TASK_DISPATCH_TOPIC:
                await self._process_task_dispatch_event(event)
            else:
                # Unexpected
                logger.error("Receive unknown topic event, unexpected!!")
        except Exception as exc:
            logger.exception(exc)
        finally:
            task.ack(event)

    ################################################################################################
    # 事件处理
    ################################################################################################
    async def _process_task_dispatch_event(self, event: Event):
        """Process diagnosis task dispatch event
        {
            "task_id": "xxx"
        }
        """
        try:
            if isinstance(event.value, dict):
                await self._execute_diagnosis_task_by_id(event.value["task_id"])
            else:
                raise Exception("expect event.value is dict")
        except Exception as exc:
            logger.exception(f"Diagnosis process task dispatch event error: {str(exc)}")

    ################################################################################################
    # 诊断任务执行
    ################################################################################################

    async def _execute_diagnosis_task_by_id(self, task_id: str):
        instance = await sync_to_async(JobModel.objects.get)(task_id=task_id)
        await self._execute_diagnosis_task_by_model(instance)

    async def _execute_diagnosis_task_by_model(self, instance: JobModel):
        # 1. Preprocess
        res = await DiagnosisHelper.preprocess_async(instance)

        if not res:
            raise Exception("Diagnosis preprocess error, DiagnosisTask is None")

        # 1.1 Preprocess post wrapper
        is_offline = await DiagnosisHelper.preprocess_post_wrapper_async(instance, res)
        await DiagnosisHelper._update_job_async(instance, status="Running")
        if is_offline:
            return

        # 2. Execute and Postprocess
        if not res.offline_mode:
            job_result = await DiagnosisHelper.execute_async(instance, res)
        else:
            job_result = DiagnosisTaskResult(
                0,
                job_results=[
                    DiagnosisJobResult(
                        0,
                        stdout=item,
                        job=res.jobs[idx] if len(res.jobs) > idx else None,
                    )
                    for idx, item in enumerate(res.offline_results)
                ],
                in_order=res.in_order,
            )
        await DiagnosisHelper.postprocess_async(instance, job_result)

        # 3. TODO: produce task execute result to cec

    ################################################################################################
    # 轮询检查任务是否超时
    ################################################################################################
    def _check_task_timeout(self):
        # Check and mark timeout tasks
        expire_minutes_ago = datetime.now() - timedelta(minutes=self._task_execute_timeout)
        instances = JobModel.objects.filter(
            Q(created_at__lte=expire_minutes_ago) & Q(status="Running")
        )
        for instance in instances:
            instance.code = 1
            instance.status = "Fail"
            instance.result = "Diagnosis execute task timeout"
            instance.err_msg = "Diagnosis execute task timeout"
            instance.save()

    def _check_task_thead(self):
        """check channel job thead schedule
        """
        self._check_task_schedule.every(self._check_interval)\
            .seconds.do(self._check_task_timeout)
        
        while True:
            if not self._check_task_process_thread.stopped() \
                and self._check_task_process_thread.is_alive():
                self._check_task_schedule.run_pending()
                time.sleep(self._check_interval * 0.8)

    def start(self):
        super().start()

        if self._check_task_process_thread is not None \
            and not self._check_task_process_thread.stopped() \
            and self._check_task_process_thread.is_alive():
            return

        self._check_task_process_thread = StoppableThread(target=self._check_task_thead)
        self._check_task_process_thread.setDaemon(True)
        self._check_task_process_thread.start()
